#include "Texture/Texture2D.h"

#include "Object/NewObject.h"
#include "Texture2D.h"

#include <bit>

#include "BMPReadWrite.h"
#include "PNGReadWrite.h"
#include "TGAReadWrite.h"
#include "JPGReadWrite.h"

#include "Output/Console.h"

#include "Render/RenderContext.h"
#include "Render/Texture/RenderTexture2D.h"

TSharedObjectPtr<RTexture2D> RTexture2D::CreateFromRawData(
    TVector<uint8_t>&& InRawData
    , uint64_t ImagePitch
    , const SIntPoint & ImageSize
    , ETextureFormat ImageFormat
    ,RObject* InOwner
    )
{
    TSharedObjectPtr<RTexture2D> Ret=NewObject<RTexture2D>(InOwner);
    Ret->Width=ImageSize.X;
    Ret->Height=ImageSize.Y;
    Ret->Pitch=ImagePitch;
    Ret->Format=ImageFormat;
    Ret->Data=std::forward<TVector<uint8_t>>(InRawData);

    Ret->SyncToRenderThread();
    return Ret;
}

bool RTexture2D::ParseImageFile(const SPath &ImageFilePath, STexture2DCreateInfo &OutCreateInfo)
{
   assert(ImageFilePath.Exists() && "file is not exist");

    if(ImageFilePath.IsFileWithExtension(U".bmp"))
    {
        return WH::ReadBMP(ImageFilePath,OutCreateInfo.RGBData,OutCreateInfo.ImageFormat,OutCreateInfo.ImageWidth,OutCreateInfo.ImageHeight,OutCreateInfo.ImagePitch);
    }

    if(ImageFilePath.IsFileWithExtension(U".png"))
    {
        return WH::ReadPNG(ImageFilePath,OutCreateInfo.RGBData,OutCreateInfo.ImageFormat,OutCreateInfo.ImageWidth,OutCreateInfo.ImageHeight,OutCreateInfo.ImagePitch);
    }

    if(ImageFilePath.IsFileWithExtension(U".tga"))
    {
        return WH::ReadTGA(ImageFilePath,OutCreateInfo.RGBData,OutCreateInfo.ImageFormat,OutCreateInfo.ImageWidth,OutCreateInfo.ImageHeight,OutCreateInfo.ImagePitch);
    }

    if(ImageFilePath.IsFileWithExtension(U".jpg") || ImageFilePath.IsFileWithExtension(U".jpeg") )
    {
        return WH::ReadJPG(ImageFilePath,OutCreateInfo.RGBData,OutCreateInfo.ImageFormat,OutCreateInfo.ImageWidth,OutCreateInfo.ImageHeight,OutCreateInfo.ImagePitch);
    }

    return false;
}

TSharedObjectPtr<RTexture2D> RTexture2D::CreateFromImage(const SPath &ImageFilePath,RObject* InOwner)
{

    STexture2DCreateInfo CreateInfo;
    if(!ParseImageFile(ImageFilePath,CreateInfo))
    {
        assert(false && "read image failed");
        return nullptr;
    }

    return CreateFromRawData(std::move(CreateInfo.RGBData),CreateInfo.ImagePitch,SIntPoint(CreateInfo.ImageWidth,CreateInfo.ImageHeight),CreateInfo.ImageFormat,InOwner);
}

TSharedObjectPtr<RTexture2D> RTexture2D::CreateFromPNGFileData(const TVector<uint8_t> &InPNGFileData,RObject* InOwner)
{

    TVector<uint8_t> Buffer;
    ETextureFormat Format;
    uint64_t Width;
    uint64_t Height;
    uint64_t Pitch;

    if(!WH::ReadPNGFromFileData(InPNGFileData,Buffer,Format,Width,Height,Pitch))
    {
        assert(false && "read png failed");
        return nullptr;
    }

    return CreateFromRawData(std::move(Buffer),Pitch,SIntPoint(Width,Height),Format,InOwner);
}

RTexture2D::STexture2DCreateInfo RTexture2D::CreateCreateInfoFromPNGFileData(const TVector<uint8_t> &InPNGFileData)
{
    STexture2DCreateInfo Ret;
    if(!WH::ReadPNGFromFileData(InPNGFileData,Ret.RGBData,Ret.ImageFormat,Ret.ImageWidth,Ret.ImageHeight,Ret.ImagePitch))
    {
        assert(false && "read png failed");
    }
    return Ret;
}

TSharedObjectPtr<RTexture2D> RTexture2D::CreateFromCreateInfo(STexture2DCreateInfo &&InCreateInfo,RObject* InOwner)
{
    return CreateFromRawData(
        std::move(InCreateInfo.RGBData)
        ,InCreateInfo.ImagePitch
        ,SIntPoint(InCreateInfo.ImageWidth,InCreateInfo.ImageHeight)
        ,InCreateInfo.ImageFormat
        ,InOwner
        );
}

RTexture2D::RTexture2D()
{
    RenderTexture = std::make_shared< CRenderTexture2D>();
}

RTexture2D:: ~RTexture2D()
{
    
}

bool RTexture2D::IsValid()
{
    return !Data.Empty()
        && bCanRender
    ;
}

void RTexture2D::PostLoadFromAsset()
{
    SyncToRenderThread();
}

void RTexture2D::SyncToRenderThread()
{
    RenderTexture->SyncToRenderThread(Data,Width,Height,Pitch,Format);
    if(Data.Empty())
    {
        bCanRender=false;
    }
    else
    {
        bCanRender=true;
    }
}

std::shared_ptr<CRenderTexture2D> RTexture2D::GetRenderTexture()
{
    return RenderTexture;
}

void RTexture2D::SaveToPNG(TSharedObjectPtr<RTexture2D> InImage,const SPath& InPngFilePath)
{
    if(!WH::WritePNG(InPngFilePath,InImage->Data,InImage->GetFormat(),InImage->GetWidth(),InImage->GetHeight(),InImage->Pitch))
    {
        assert(false && "write png failed");
    }
    return;
}

void RTexture2D::SaveToPNGFileData(TSharedObjectPtr<RTexture2D> InImage, TVector<uint8_t> &OutPNGFileData)
{
    if(!WH::WritePNGToFileData(InImage->Data,InImage->GetFormat(),InImage->GetWidth(),InImage->GetHeight(),InImage->Pitch,OutPNGFileData))
    {
        assert(false && "write png failed");
    }
    return;
}

TSharedObjectPtr<RTexture2D> RTexture2D::Resize(TSharedObjectPtr<RTexture2D> InOrignalImage, const SIntPoint &NewSize)
{
    assert(InOrignalImage->GetFormat()==ETextureFormat::R8G8B8A8 && "only support R8G8B8A8 format");

    TVector<uint8_t> PixelData;
    PixelData.Resize(NewSize.X*NewSize.Y*4);
    uint64_t Pitch=NewSize.X*4;

    SVec2 Scale=SVec2(InOrignalImage->GetWidth()/(float)NewSize.X,InOrignalImage->GetHeight()/(float)NewSize.Y);

    for(uint32_t X=0;X<NewSize.X;X++)
    {
        for(uint32_t Y=0;Y<NewSize.Y;Y++)
        {
            SVec2 OrignalPixel=SVec2(Scale.X*X,Scale.Y*Y);
            
            SIntPoint LeftTopPixel=SIntPoint(OrignalPixel);
            SIntPoint RightTop=LeftTopPixel+SIntPoint(1,0);
            SIntPoint LeftBottom=LeftTopPixel+SIntPoint(0,1);
            SIntPoint RightBottom=LeftTopPixel+SIntPoint(1,1);

            float BottomWeight=OrignalPixel.Y-LeftTopPixel.Y;
            float TopWeight=1-BottomWeight;

            float LeftWeight=RightTop.X-OrignalPixel.X;
            float RightWeight=1-LeftWeight;

            uint8_t* ResultR=PixelData.Data()+ Y*Pitch +X*4;
            uint8_t* ResultG=ResultR+1;
            uint8_t* ResultB=ResultR+2;
            uint8_t* ResultA=ResultR+3;


            auto GetOrignalRGBA= [InOrignalImage](const SIntPoint& InPixel){
                uint8_t R,G,B,A;

                uint8_t* PxielData=InOrignalImage->Data.Data() + InOrignalImage->Pitch *InPixel.Y+ InPixel.X*4;
                R=*PxielData;
                G=*(PxielData+1);
                B=*(PxielData+2);
                A=*(PxielData+3);

                return WH::MakeTuple(R,G,B,A);
            };

            if(TopWeight==1 && LeftWeight==1)
            {
                auto RGBA=GetOrignalRGBA(LeftTopPixel);
                *ResultR=RGBA.First;
                *ResultG=RGBA.Second;
                *ResultB=RGBA.Third;
                *ResultA=RGBA.Fourth;
            }
            else if(TopWeight==1)
            {
                auto LeftRGBA=GetOrignalRGBA(LeftTopPixel);
                auto RightRGBA=GetOrignalRGBA(RightTop);

                *ResultR=LeftRGBA.First *LeftWeight + RightRGBA.First *RightWeight ;
                *ResultG=LeftRGBA.Second *LeftWeight + RightRGBA.Second *RightWeight ;
                *ResultB=LeftRGBA.Third *LeftWeight + RightRGBA.Third *RightWeight ;
                *ResultA=LeftRGBA.Fourth *LeftWeight + RightRGBA.Fourth *RightWeight ;               
            }
            else if(LeftWeight==1)
            {
                auto TopRGBA=GetOrignalRGBA(LeftTopPixel);
                auto BottomRGBA=GetOrignalRGBA(LeftBottom);

                *ResultR=TopRGBA.First *TopWeight + BottomRGBA.First *BottomWeight ;
                *ResultG=TopRGBA.Second *TopWeight + BottomRGBA.Second *BottomWeight ;
                *ResultB=TopRGBA.Third *TopWeight + BottomRGBA.Third *BottomWeight ;
                *ResultA=TopRGBA.Fourth *TopWeight + BottomRGBA.Fourth *BottomWeight ;               
            }
            else
            {
                auto LeftTopRGBA=GetOrignalRGBA(LeftTopPixel);
                auto LeftBottomRGBA=GetOrignalRGBA(LeftBottom);
                auto RightTopRGBA=GetOrignalRGBA(RightTop);
                auto RightBottomRGBA=GetOrignalRGBA(RightBottom);

                *ResultR=LeftTopRGBA.First*LeftWeight*TopWeight + LeftBottomRGBA.First * LeftWeight*BottomWeight +RightTopRGBA.First *RightWeight *TopWeight+RightBottomRGBA.First *RightWeight*BottomWeight;
                *ResultG=LeftTopRGBA.Second*LeftWeight*TopWeight + LeftBottomRGBA.Second * LeftWeight*BottomWeight +RightTopRGBA.Second *RightWeight *TopWeight+RightBottomRGBA.Second *RightWeight*BottomWeight;
                *ResultB=LeftTopRGBA.Third*LeftWeight*TopWeight + LeftBottomRGBA.Third * LeftWeight*BottomWeight +RightTopRGBA.Third *RightWeight *TopWeight+RightBottomRGBA.Third *RightWeight*BottomWeight;
                *ResultA=LeftTopRGBA.Fourth*LeftWeight*TopWeight + LeftBottomRGBA.Fourth * LeftWeight*BottomWeight +RightTopRGBA.Fourth *RightWeight *TopWeight+RightBottomRGBA.Fourth *RightWeight*BottomWeight;            
            }

        }

    }

    return CreateFromRawData(std::move(PixelData),Pitch,NewSize,InOrignalImage->GetFormat());
}

void RTexture2D::HorizontalFlip(TVector<uint8_t> &InOutData, int64_t ImageWidth, int64_t ImageHeight, int64_t ImagePitch, int64_t BytesPerPixel)
{
    for(int64_t Y=0;Y<ImageHeight;Y++)
    {
        for(int64_t X=0;X<ImageWidth/2;X++)
        {
            uint8_t* LeftPixel=InOutData.Data()+Y*ImagePitch+X*BytesPerPixel;
            uint8_t* RightPixel=InOutData.Data()+Y*ImagePitch+(ImageWidth-1-X)*BytesPerPixel;

            for(int64_t ByteIndex=0;ByteIndex<BytesPerPixel;ByteIndex++)
            {
                std::swap(LeftPixel[ByteIndex],RightPixel[ByteIndex]);
            }
        }
    }
}

void RTexture2D::HorizontalFlip(TVector<uint8_t> &InOutData, int64_t ImageWidth, int64_t ImageHeight, int64_t ImagePitch, ETextureFormat Format)
{
    switch (Format)
    {
        case ETextureFormat::R8G8B8A8:
            return HorizontalFlip(InOutData,ImageWidth,ImageHeight,ImagePitch,4);
        case ETextureFormat::R8:
            return HorizontalFlip(InOutData,ImageWidth,ImageHeight,ImagePitch,1);
        default:
            assert(false && "not support format");
            break;
    }
}
